/* proto.c
 * Routines for protocol tree
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

// NOLINTNEXTLINE(misc-no-recursion)
static bool proto_tree_traverse_pre_order(proto_tree *tree, proto_tree_traverse_func func,
                  void *data)
{
    proto_node *pnode = tree;
    proto_node *child;
    proto_node *current;

    if (func(pnode, data))
        return true;

    child = pnode->first_child;
    while (child != NULL) {
        /*
         * The routine we call might modify the child, e.g. by
         * freeing it, so we get the child's successor before
         * calling that routine.
         */
        current = child;
        child   = current->next;
        // We recurse here, but we're limited by prefs.gui_max_tree_depth
        if (proto_tree_traverse_pre_order((proto_tree *)current, func, data))
            return true;
    }

    return false;
}

void proto_tree_children_foreach(proto_tree *tree, proto_tree_foreach_func func,
                void *data)
{
    proto_node *node = tree;
    proto_node *current;

    if (!node)
        return;

    node = node->first_child;
    while (node != NULL) {
        current = node;
        node    = current->next;
        func((proto_tree *)current, data);
    }
}

static void free_GPtrArray_value(void *key, void *value, void *user_data)
{
    GPtrArray         *ptrs = (GPtrArray *)value;
    int                hfid = GPOINTER_TO_UINT(key);
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfid, hfinfo);
    if (hfinfo->ref_type != HF_REF_TYPE_NONE) {
        /* when a field is referenced by a filter this also
           affects the refcount for the parent protocol so we need
           to adjust the refcount for the parent as well
        */
        if (hfinfo->parent != -1) {
            hf_info *parent_hfinfo;
            PROTO_REGISTRAR_GET_NTH(hfinfo->parent, parent_hfinfo);
            parent_hfinfo->ref_type = HF_REF_TYPE_NONE;
        }
        hfinfo->ref_type = HF_REF_TYPE_NONE;
    }

    g_ptr_array_free(ptrs, true);
}

static void proto_tree_free_node(proto_node *node, void *data)
{
    field_info *finfo  = PNODE_FINFO(node);

    proto_tree_children_foreach(node, proto_tree_free_node, NULL);

    fvalue_free(finfo->value);
    finfo->value = NULL;
}

void proto_tree_reset(proto_tree *tree)
{
    tree_data_t *tree_data = PTREE_DATA(tree);

    proto_tree_children_foreach(tree, proto_tree_free_node, NULL);

    /* free tree data */
    if (tree_data->interesting_hfids) {
        /* Free all the GPtrArray's in the interesting_hfids hash. */
        g_hash_table_foreach(tree_data->interesting_hfids,
            free_GPtrArray_value, NULL);

        /* And then remove all values. */
        g_hash_table_remove_all(tree_data->interesting_hfids);
    }

    /* Reset track of the number of children */
    tree_data->count = 0;

    PROTO_NODE_INIT(tree);
}

/* frees the resources that the dissection a proto_tree uses */
void proto_tree_free(proto_tree *tree)
{
    tree_data_t *tree_data = PTREE_DATA(tree);

    proto_tree_children_foreach(tree, proto_tree_free_node, NULL);

    /* free tree data */
    if (tree_data->interesting_hfids) {
        /* Free all the GPtrArray's in the interesting_hfids hash. */
        g_hash_table_foreach(tree_data->interesting_hfids,
            free_GPtrArray_value, NULL);

        /* And then destroy the hash. */
        g_hash_table_destroy(tree_data->interesting_hfids);
    }

    g_slice_free(tree_data_t, tree_data);

    g_slice_free(proto_tree, tree);
}

/* Is the parsing being done for a visible proto_tree or an invisible one?
 * By setting this correctly, the proto_tree creation is sped up by not
 * having to call vsnprintf and copy strings around.
 */
bool proto_tree_set_visible(proto_tree *tree, bool visible)
{
    bool old_visible = PTREE_DATA(tree)->visible;

    PTREE_DATA(tree)->visible = visible;

    return old_visible;
}

void proto_tree_set_fake_protocols(proto_tree *tree, bool fake_protocols)
{
    PTREE_DATA(tree)->fake_protocols = fake_protocols;
}


static int label_strcat_flags(const hf_info *hfinfo)
{
    if (FIELD_DISPLAY(hfinfo->display) & BASE_STR_WSP)
        return FORMAT_LABEL_REPLACE_SPACE;

    return 0;
}

static char *format_bytes_hfinfo_maxlen(const hf_info *hfinfo,
    const uint8_t *bytes, unsigned length, size_t max_str_len)
{
    char *str = NULL;
    const uint8_t *p;
    bool is_printable;

    if (bytes) {
        if (hfinfo->display & BASE_SHOW_UTF_8_PRINTABLE) {
            /*
             * If all bytes are valid and printable UTF-8, show the
             * bytes as a string - in quotes to indicate that it's
             * a string.
             */
            if (isprint_utf8_string(bytes, length)) {
                str = malloc(256);
                snprintf(str, 256, "\"%.*s\"",
                         (int)length, bytes);
//                str = wmem_strdup_printf("\"%.*s\"",
//                    (int)length, bytes);
                return str;
            }
        } else if (hfinfo->display & BASE_SHOW_ASCII_PRINTABLE) {
            /*
             * Check whether all bytes are printable.
             */
            is_printable = true;
            for (p = bytes; p < bytes+length; p++) {
                if (!g_ascii_isprint(*p)) {
                    /* Not printable. */
                    is_printable = false;
                    break;
                }
            }

            /*
             * If all bytes are printable ASCII, show the bytes
             * as a string - in quotes to indicate that it's
             * a string.
             */
            if (is_printable) {
                str = malloc(256);
                snprintf(str, 256, "\"%.*s\"",
                         (int)length, bytes);
//                str = wmem_strdup_printf("\"%.*s\"",
//                    (int)length, bytes);
                return str;
            }
        }

        /*
         * Either it's not printable ASCII, or we don't care whether
         * it's printable ASCII; show it as hex bytes.
         */
        switch (FIELD_DISPLAY(hfinfo->display)) {
        case SEP_DOT:
            str = bytes_to_str_punct_maxlen(bytes, length, '.', max_str_len/3);
            break;
        case SEP_DASH:
            str = bytes_to_str_punct_maxlen(bytes, length, '-', max_str_len/3);
            break;
        case SEP_COLON:
            str = bytes_to_str_punct_maxlen(bytes, length, ':', max_str_len/3);
            break;
        case SEP_SPACE:
            str = bytes_to_str_punct_maxlen(bytes, length, ' ', max_str_len/3);
            break;
        case BASE_NONE:
        default:
            if (true/*prefs.display_byte_fields_with_spaces*/) {
                str = bytes_to_str_punct_maxlen(bytes, length, ' ', max_str_len/3);
            } else {
                str = bytes_to_str_maxlen(bytes, length, max_str_len/2);
            }
            break;
        }
    }
    else {
        if (hfinfo->display & BASE_ALLOW_ZERO) {
            str = strdup("<none>");
        } else {
            str = strdup("<MISSING>");
        }
    }
    return str;
}

static char *format_bytes_hfinfo(const hf_info *hfinfo,
    const uint8_t *bytes, unsigned length)
{
    return format_bytes_hfinfo_maxlen(hfinfo, bytes, length, ITEM_LABEL_LENGTH);
}

/* Add a text-only node, leaving it to our caller to fill the text in */
static proto_item *proto_tree_add_text_node(proto_tree *tree, tvbuff_t *tvb, int start, int length)
{
    proto_item *pi;

    if (tree == NULL)
        return NULL;

    pi = proto_tree_add_pi(tree, &hfi_text_only, tvb, start, &length);

    return pi;
}

/* (INTERNAL USE ONLY) Add a text-only node to the proto_tree */
proto_item *proto_tree_add_text_internal(proto_tree *tree, tvbuff_t *tvb, int start, int length,
            const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;
    hf_info *hfinfo;

    if (length == -1) {
        length = tvb_captured_length(tvb) ? tvb_ensure_captured_length_remaining(tvb, start) : 0;
    } else {
        tvb_ensure_bytes_exist(tvb, start, length);
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hf_text_only, hfinfo);

    pi = proto_tree_add_text_node(tree, tvb, start, length);

    TRY_TO_FAKE_THIS_REPR(pi);

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);

    return pi;
}

/* We could probably get away with changing is_error to a minimum length value. */
static void report_type_length_mismatch(proto_tree *tree, const char *descr, int length, bool is_error)
{
//    if (is_error) {
//        expert_add_info_format(NULL, tree, &ei_type_length_mismatch_error, "Trying to fetch %s with length %d", descr, length);
//    } else {
//        expert_add_info_format(NULL, tree, &ei_type_length_mismatch_warn, "Trying to fetch %s with length %d", descr, length);
//    }

//    if (is_error) {
//        THROW(ReportedBoundsError);
//    }
}

static uint32_t get_uint_value(proto_tree *tree, tvbuff_t *tvb, int offset, int length, const unsigned encoding)
{
    uint32_t value;
    bool length_error;

    switch (length) {
    case 1:
        value = tvb_get_uint8(tvb, offset);
        if (encoding & ENC_ZIGBEE) {
            if (value == 0xFF) { /* Invalid Zigbee length, set to 0 */
                value = 0;
            }
        }
        break;
    case 2:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohs(tvb, offset)
                               : tvb_get_ntohs(tvb, offset);
        if (encoding & ENC_ZIGBEE) {
            if (value == 0xFFFF) { /* Invalid Zigbee length, set to 0 */
                value = 0;
            }
        }
        break;
    case 3:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh24(tvb, offset)
                               : tvb_get_ntoh24(tvb, offset);
        break;
    case 4:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohl(tvb, offset)
                               : tvb_get_ntohl(tvb, offset);
        break;
    default:
        if (length < 1) {
            length_error = true;
            value = 0;
        } else {
            length_error = false;
            value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohl(tvb, offset)
                                   : tvb_get_ntohl(tvb, offset);
        }
        report_type_length_mismatch(tree, "an unsigned integer", length, length_error);
        break;
    }
    return value;
}

static inline uint64_t get_uint64_value(proto_tree *tree, tvbuff_t *tvb, int offset, unsigned length, const unsigned encoding)
{
    uint64_t value;
    bool length_error;

    switch (length) {
    case 1:
        value = tvb_get_uint8(tvb, offset);
        break;
    case 2:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohs(tvb, offset)
                               : tvb_get_ntohs(tvb, offset);
        break;
    case 3:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh24(tvb, offset)
                               : tvb_get_ntoh24(tvb, offset);
        break;
    case 4:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohl(tvb, offset)
                               : tvb_get_ntohl(tvb, offset);
        break;
    case 5:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh40(tvb, offset)
                               : tvb_get_ntoh40(tvb, offset);
        break;
    case 6:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh48(tvb, offset)
                               : tvb_get_ntoh48(tvb, offset);
        break;
    case 7:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh56(tvb, offset)
                               : tvb_get_ntoh56(tvb, offset);
        break;
    case 8:
        value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh64(tvb, offset)
                               : tvb_get_ntoh64(tvb, offset);
        break;
    default:
        if (length < 1) {
            length_error = true;
            value = 0;
        } else {
            length_error = false;
            value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh64(tvb, offset)
                                   : tvb_get_ntoh64(tvb, offset);
        }
        report_type_length_mismatch(tree, "an unsigned integer", length, length_error);
        break;
    }
    return value;
}

static int32_t get_int_value(proto_tree *tree, tvbuff_t *tvb, int offset, int length, const unsigned encoding)
{
    int32_t value;
    bool length_error;

    switch (length) {
    case 1:
        value = tvb_get_int8(tvb, offset);
        break;
    case 2:
        value = encoding ? tvb_get_letohis(tvb, offset)
                 : tvb_get_ntohis(tvb, offset);
        break;
    case 3:
        value = encoding ? tvb_get_letohi24(tvb, offset)
                 : tvb_get_ntohi24(tvb, offset);
        break;
    case 4:
        value = encoding ? tvb_get_letohil(tvb, offset)
                 : tvb_get_ntohil(tvb, offset);
        break;
    default:
        if (length < 1) {
            length_error = true;
            value = 0;
        } else {
            length_error = false;
            value = encoding ? tvb_get_letohil(tvb, offset)
                     : tvb_get_ntohil(tvb, offset);
        }
        report_type_length_mismatch(tree, "a signed integer", length, length_error);
        break;
    }
    return value;
}

/* Note: this returns an unsigned int64, but with the appropriate bit(s) set to
 * be cast-able as a int64_t. This is weird, but what the code has always done.
 */
static inline uint64_t get_int64_value(proto_tree *tree, tvbuff_t *tvb, int start, unsigned length, const unsigned encoding)
{
    uint64_t value = get_uint64_value(tree, tvb, start, length, encoding);

    switch (length) {
    case 7:
        value = ws_sign_ext64(value, 56);
        break;
    case 6:
        value = ws_sign_ext64(value, 48);
        break;
    case 5:
        value = ws_sign_ext64(value, 40);
        break;
    case 4:
        value = ws_sign_ext64(value, 32);
        break;
    case 3:
        value = ws_sign_ext64(value, 24);
        break;
    case 2:
        value = ws_sign_ext64(value, 16);
        break;
    case 1:
        value = ws_sign_ext64(value, 8);
        break;
    }

    return value;
}

/* For FT_STRING */
static inline const uint8_t *get_string_value(tvbuff_t *tvb, int start,
    int length, int *ret_length, const unsigned encoding)
{
    if (length == -1) {
        length = tvb_ensure_captured_length_remaining(tvb, start);
    }
    *ret_length = length;
    return tvb_get_string_enc(tvb, start, length, encoding);
}

/* For FT_STRINGZ */
static inline const uint8_t *get_stringz_value(proto_tree *tree, tvbuff_t *tvb,
    int start, int length, int *ret_length, const unsigned encoding)
{
    const uint8_t *value;

    if (length < -1) {
        report_type_length_mismatch(tree, "a string", length, true);
    }
    if (length == -1) {
        /* This can throw an exception */
        value = tvb_get_stringz_enc(tvb, start, &length, encoding);
    } else {
        /* In this case, length signifies the length of the string.
         *
         * This could either be a null-padded string, which doesn't
         * necessarily have a '\0' at the end, or a null-terminated
         * string, with a trailing '\0'.  (Yes, there are cases
         * where you have a string that's both counted and null-
         * terminated.)
         *
         * In the first case, we must allocate a buffer of length
         * "length+1", to make room for a trailing '\0'.
         *
         * In the second case, we don't assume that there is a
         * trailing '\0' there, as the packet might be malformed.
         * (XXX - should we throw an exception if there's no
         * trailing '\0'?)  Therefore, we allocate a buffer of
         * length "length+1", and put in a trailing '\0', just to
         * be safe.
         *
         * (XXX - this would change if we made string values counted
         * rather than null-terminated.)
         */
        value = tvb_get_string_enc(tvb, start, length, encoding);
    }
    *ret_length = length;
    return value;
}

/* For FT_UINT_STRING */
static inline const uint8_t *get_uint_string_value(proto_tree *tree,
    tvbuff_t *tvb, int start, int length, int *ret_length,
    const unsigned encoding)
{
    uint32_t n;
    const uint8_t *value;

    /* I believe it's ok if this is called with a NULL tree */
    n = get_uint_value(tree, tvb, start, length, encoding & ~ENC_CHARENCODING_MASK);
    value = tvb_get_string_enc(tvb, start + length, n, encoding);
    length += n;
    *ret_length = length;
    return value;
}

static void tree_data_add_maybe_interesting_field(tree_data_t *tree_data, field_info *fi)
{
    const hf_info *hfinfo = fi->hfinfo;

    if (hfinfo->ref_type == HF_REF_TYPE_DIRECT || hfinfo->ref_type == HF_REF_TYPE_PRINT) {
        GPtrArray *ptrs = NULL;

        if (tree_data->interesting_hfids == NULL) {
            /* Initialize the hash because we now know that it is needed */
            tree_data->interesting_hfids =
                g_hash_table_new(g_direct_hash, NULL /* g_direct_equal */);
        } else if (g_hash_table_size(tree_data->interesting_hfids)) {
            ptrs = (GPtrArray *)g_hash_table_lookup(tree_data->interesting_hfids,
                       GINT_TO_POINTER(hfinfo->id));
        }

        if (!ptrs) {
            /* First element triggers the creation of pointer array */
            ptrs = g_ptr_array_new();
            g_hash_table_insert(tree_data->interesting_hfids,
                        GINT_TO_POINTER(hfinfo->id), ptrs);
        }

        g_ptr_array_add(ptrs, fi);
    }
}

/*
 * Validates that field length bytes are available starting from
 * start (pos/neg). Throws an exception if they aren't.
 */
static void test_length(hf_info *hfinfo, tvbuff_t *tvb,
        int start, int length, const unsigned encoding)
{
    int size = length;

    if (!tvb)
        return;

    if ((hfinfo->type == FT_STRINGZ) ||
        ((encoding & ENC_VARINT_MASK) &&
         (FT_IS_UINT(hfinfo->type) || FT_IS_INT(hfinfo->type)))) {
        /* If we're fetching until the end of the TVB, only validate
         * that the offset is within range.
         */
        if (length == -1)
            size = 0;
    }

    tvb_ensure_bytes_exist(tvb, start, size);
}

static void detect_trailing_stray_characters(unsigned encoding, const char *string, int length, proto_item *pi)
{// TODO: maybe will delete it
    bool found_stray_character = false;

    if (!string)
        return;

    switch (encoding & ENC_CHARENCODING_MASK) {
    case ENC_ASCII:
    case ENC_UTF_8:
        for (int i = (int)strlen(string); i < length; i++) {
            if (string[i] != '\0') {
                found_stray_character = true;
                break;
            }
        }
        break;
    default:
        break;
    }

//    if (found_stray_character) {
//        expert_add_info(NULL, pi, &ei_string_trailing_characters);
//    }
}

static void free_fvalue_cb(void *data)
{
    fvalue_t *fv = (fvalue_t*)data;
    fvalue_free(fv);
}

/* Add an item to a proto_tree, using the text label registered to that item;
   the item is extracted from the tvbuff handed to it. */
static proto_item *proto_tree_new_item(field_info *new_fi, proto_tree *tree,
            tvbuff_t *tvb, int start, int length,
            unsigned encoding)
{
    proto_item *pi;
    uint32_t        value, n;
    uint64_t        value64;
//    ws_in4_addr ipv4_value;
    float        floatval;
    double        doubleval;
    const char *stringval = NULL;
//    nstime_t    time_stamp;
    bool        length_error;

    /* Ensure that the newly created fvalue_t is freed if we throw an
     * exception before adding it to the tree. (gcc creates clobbering
     * when it optimizes the equivalent TRY..EXCEPT implementation.)
     * XXX: Move the new_field_info() call inside here?
     */
//    CLEANUP_PUSH(free_fvalue_cb, new_fi->value);

    switch (new_fi->hfinfo->type) {
        case FT_NONE:
            /* no value to set for FT_NONE */
            break;
        case FT_PROTOCOL:
            proto_tree_set_protocol_tvb(new_fi, tvb, new_fi->hfinfo->name, length);
            break;
        case FT_BYTES:
            proto_tree_set_bytes_tvb(new_fi, tvb, start, length);
            break;
        case FT_UINT_BYTES:
            n = get_uint_value(tree, tvb, start, length, encoding);
            proto_tree_set_bytes_tvb(new_fi, tvb, start + length, n);

            /* Instead of calling proto_item_set_len(), since we don't yet
             * have a proto_item, we set the field_info's length ourselves. */
            new_fi->length = n + length;
            break;
        case FT_BOOLEAN:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            proto_tree_set_boolean(new_fi,
                get_uint64_value(tree, tvb, start, length, encoding));
            break;
        case FT_CHAR:
        /* XXX - make these just FT_UINT? */
        case FT_UINT8:
        case FT_UINT16:
        case FT_UINT24:
        case FT_UINT32:
            if (encoding & ENC_VARINT_MASK) {
                new_fi->length = tvb_get_varint(tvb, start, (length == -1) ? FT_VARINT_MAX_LEN : length, &value64, encoding);
                value = (uint32_t)value64;
                if (!(encoding & ENC_VARINT_QUIC)) {
                    new_fi->flags |= FI_VARINT;
                }
            } else {
                /*
                 * Map all non-zero values to little-endian for
                 * backwards compatibility.
                 */
                if (encoding)
                    encoding = ENC_LITTLE_ENDIAN;

                value = get_uint_value(tree, tvb, start, length, encoding);
            }
            proto_tree_set_uint(new_fi, value);
            break;
        case FT_UINT40:
        case FT_UINT48:
        case FT_UINT56:
        case FT_UINT64:
            if (encoding & ENC_VARINT_MASK) {
                new_fi->length = tvb_get_varint(tvb, start, (length == -1) ? FT_VARINT_MAX_LEN : length, &value64, encoding);
                if (!(encoding & ENC_VARINT_QUIC)) {
                    new_fi->flags |= FI_VARINT;
                }
            } else {
                /*
                 * Map all other non-zero values to little-endian for
                 * backwards compatibility.
                 */
                if (encoding)
                    encoding = ENC_LITTLE_ENDIAN;

                value64 = get_uint64_value(tree, tvb, start, length, encoding);
            }
            proto_tree_set_uint64(new_fi, value64);
            break;
        /* XXX - make these just FT_INT? */
        case FT_INT8:
        case FT_INT16:
        case FT_INT24:
        case FT_INT32:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            proto_tree_set_int(new_fi,
                get_int_value(tree, tvb, start, length, encoding));
            break;
        case FT_INT40:
        case FT_INT48:
        case FT_INT56:
        case FT_INT64:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            proto_tree_set_int64(new_fi,
                get_int64_value(tree, tvb, start, length, encoding));
            break;
#if 0
        case FT_IPv4:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != FT_IPv4_LEN) {
                length_error = length < FT_IPv4_LEN ? true : false;
                report_type_length_mismatch(tree, "an IPv4 address", length, length_error);
            }
            ipv4_value = tvb_get_ipv4(tvb, start);
            /*
             * NOTE: to support code written when
             * proto_tree_add_item() took a bool as its
             * last argument, with false meaning "big-endian"
             * and true meaning "little-endian", we treat any
             * non-zero value of "encoding" as meaning
             * "little-endian".
             */
            proto_tree_set_ipv4(new_fi, encoding ? GUINT32_SWAP_LE_BE(ipv4_value) : ipv4_value);
            break;
        case FT_IPXNET:
            if (length != FT_IPXNET_LEN) {
                length_error = length < FT_IPXNET_LEN ? true : false;
                report_type_length_mismatch(tree, "an IPXNET address", length, length_error);
            }
            proto_tree_set_ipxnet(new_fi,
                get_uint_value(tree, tvb, start, FT_IPXNET_LEN, ENC_BIG_ENDIAN));
            break;
        case FT_IPv6:
            if (length != FT_IPv6_LEN) {
                length_error = length < FT_IPv6_LEN ? true : false;
                report_type_length_mismatch(tree, "an IPv6 address", length, length_error);
            }
            proto_tree_set_ipv6_tvb(new_fi, tvb, start, length);
            break;
        case FT_FCWWN:
            if (length != FT_FCWWN_LEN) {
                length_error = length < FT_FCWWN_LEN ? true : false;
                report_type_length_mismatch(tree, "an FCWWN address", length, length_error);
            }
            proto_tree_set_fcwwn_tvb(new_fi, tvb, start, length);
            break;

        case FT_AX25:
            if (length != 7) {
                length_error = length < 7 ? true : false;
                report_type_length_mismatch(tree, "an AX.25 address", length, length_error);
            }
            proto_tree_set_ax25_tvb(new_fi, tvb, start);
            break;

        case FT_VINES:
            if (length != VINES_ADDR_LEN) {
                length_error = length < VINES_ADDR_LEN ? true : false;
                report_type_length_mismatch(tree, "a Vines address", length, length_error);
            }
            proto_tree_set_vines_tvb(new_fi, tvb, start);
            break;
        case FT_ETHER:
            if (length != FT_ETHER_LEN) {
                length_error = length < FT_ETHER_LEN ? true : false;
                report_type_length_mismatch(tree, "a MAC address", length, length_error);
            }
            proto_tree_set_ether_tvb(new_fi, tvb, start);
            break;
        case FT_EUI64:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != FT_EUI64_LEN) {
                length_error = length < FT_EUI64_LEN ? true : false;
                report_type_length_mismatch(tree, "an EUI-64 address", length, length_error);
            }
            proto_tree_set_eui64_tvb(new_fi, tvb, start, encoding);
            break;
        case FT_GUID:
            /*
             * Map all non-zero values to little-endian for
             * backwards compatibility.
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != FT_GUID_LEN) {
                length_error = length < FT_GUID_LEN ? true : false;
                report_type_length_mismatch(tree, "a GUID", length, length_error);
            }
            proto_tree_set_guid_tvb(new_fi, tvb, start, encoding);
            break;
        case FT_OID:
        case FT_REL_OID:
            proto_tree_set_oid_tvb(new_fi, tvb, start, length);
            break;
        case FT_SYSTEM_ID:
            proto_tree_set_system_id_tvb(new_fi, tvb, start, length);
            break;
#endif
        case FT_FLOAT:
            /*
             * NOTE: to support code written when
             * proto_tree_add_item() took a bool as its
             * last argument, with false meaning "big-endian"
             * and true meaning "little-endian", we treat any
             * non-zero value of "encoding" as meaning
             * "little-endian".
             *
             * At some point in the future, we might
             * support non-IEEE-binary floating-point
             * formats in the encoding as well
             * (IEEE decimal, System/3x0, VAX).
             */
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != 4) {
                length_error = length < 4 ? true : false;
                report_type_length_mismatch(tree, "a single-precision floating point number", length, length_error);
            }
            if (encoding)
                floatval = tvb_get_letohieee_float(tvb, start);
            else
                floatval = tvb_get_ntohieee_float(tvb, start);
            proto_tree_set_float(new_fi, floatval);
            break;
        case FT_DOUBLE:
            /*
             * NOTE: to support code written when
             * proto_tree_add_item() took a bool as its
             * last argument, with false meaning "big-endian"
             * and true meaning "little-endian", we treat any
             * non-zero value of "encoding" as meaning
             * "little-endian".
             *
             * At some point in the future, we might
             * support non-IEEE-binary floating-point
             * formats in the encoding as well
             * (IEEE decimal, System/3x0, VAX).
             */
            if (encoding == true)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != 8) {
                length_error = length < 8 ? true : false;
                report_type_length_mismatch(tree, "a double-precision floating point number", length, length_error);
            }
            if (encoding)
                doubleval = tvb_get_letohieee_double(tvb, start);
            else
                doubleval = tvb_get_ntohieee_double(tvb, start);
            proto_tree_set_double(new_fi, doubleval);
            break;
        case FT_STRING:
            stringval = get_string_value(tvb, start, length, &length, encoding);
            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(), since we
             * don't yet have a proto_item, we set the
             * field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
            new_fi->length = length;
            break;
        case FT_STRINGZ:
            stringval = get_stringz_value(tree, tvb, start, length, &length, encoding);
            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(),
             * since we don't yet have a proto_item, we
             * set the field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
            new_fi->length = length;
            break;
        case FT_UINT_STRING:
            /*
             * NOTE: to support code written when
             * proto_tree_add_item() took a bool as its
             * last argument, with false meaning "big-endian"
             * and true meaning "little-endian", if the
             * encoding value is true, treat that as
             * ASCII with a little-endian length.
             *
             * This won't work for code that passes
             * arbitrary non-zero values; that code
             * will need to be fixed.
             */
            if (encoding == true)
                encoding = ENC_ASCII|ENC_LITTLE_ENDIAN;
            stringval = get_uint_string_value(tree, tvb, start, length, &length, encoding);
            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(), since we
             * don't yet have a proto_item, we set the
             * field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
            new_fi->length = length;
            break;
#if 0
        case FT_STRINGZPAD:
//            stringval = get_stringzpad_value(
//                tvb, start, length, &length, encoding);
//            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(), since we
             * don't yet have a proto_item, we set the
             * field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
//            new_fi->length = length;
            break;
        case FT_STRINGZTRUNC:
//            stringval = get_stringztrunc_value(
//                tvb, start, length, &length, encoding);
//            proto_tree_set_string(new_fi, stringval);

            /* Instead of calling proto_item_set_len(), since we
             * don't yet have a proto_item, we set the
             * field_info's length ourselves.
             *
             * XXX - our caller can't use that length to
             * advance an offset unless they arrange that
             * there always be a protocol tree into which
             * we're putting this item.
             */
//            new_fi->length = length;
            break;
        case FT_ABSOLUTE_TIME:
            /*
             * Absolute times can be in any of a number of
             * formats, and they can be big-endian or
             * little-endian.
             *
             * Historically FT_TIMEs were only timespecs;
             * the only question was whether they were stored
             * in big- or little-endian format.
             *
             * For backwards compatibility, we interpret an
             * encoding of 1 as meaning "little-endian timespec",
             * so that passing true is interpreted as that.
             */
//            if (encoding == true)
//                encoding = ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN;

//            get_time_value(tree, tvb, start, length, encoding, &time_stamp, false);

//            proto_tree_set_time(new_fi, &time_stamp);
            break;
        case FT_RELATIVE_TIME:
            /*
             * Relative times can be in any of a number of
             * formats, and they can be big-endian or
             * little-endian.
             *
             * Historically FT_TIMEs were only timespecs;
             * the only question was whether they were stored
             * in big- or little-endian format.
             *
             * For backwards compatibility, we interpret an
             * encoding of 1 as meaning "little-endian timespec",
             * so that passing true is interpreted as that.
             */
            if (encoding == true)
                encoding = ENC_TIME_SECS_NSECS|ENC_LITTLE_ENDIAN;

            get_time_value(tree, tvb, start, length, encoding, &time_stamp, true);

            proto_tree_set_time(new_fi, &time_stamp);
            break;
        case FT_IEEE_11073_SFLOAT:
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != 2) {
                length_error = length < 2 ? true : false;
                report_type_length_mismatch(tree, "a IEEE 11073 SFLOAT", length, length_error);
            }
            fvalue_set_uinteger(new_fi->value, tvb_get_uint16(tvb, start, encoding));
            break;
        case FT_IEEE_11073_FLOAT:
            if (encoding)
                encoding = ENC_LITTLE_ENDIAN;
            if (length != 4) {
                length_error = length < 4 ? true : false;
                report_type_length_mismatch(tree, "a IEEE 11073 FLOAT", length, length_error);
            }
            break;
#endif
        default:
            REPORT_DISSECTOR_BUG("field %s is of unknown type %d (%s)",
                         new_fi->hfinfo->abbrev,
                         new_fi->hfinfo->type,
                         ftype_name(new_fi->hfinfo->type));
            break;
    }
    FI_SET_FLAG(new_fi, (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN);

    /* Don't add new node to proto_tree until now so that any exceptions
     * raised by a tvbuff access method doesn't leave junk in the proto_tree. */
    /* XXX. wouldn't be better to add this item to tree, with some special
     * flag (FI_EXCEPTION?) to know which item caused exception? For
     * strings and bytes, we would have to set new_fi->value to something
     * non-NULL, or otherwise ensure that proto_item_fill_display_label
     * could handle NULL values. */
//    CLEANUP_POP
    pi = proto_tree_add_node(tree, new_fi);

    switch (new_fi->hfinfo->type) {
    case FT_STRING:
        /* XXX: trailing stray character detection should be done
             * _before_ conversion to UTF-8, because conversion can change
             * the length, or else get_string_length should return a value
             * for the "length in bytes of the string after conversion
             * including internal nulls." (Noting that we do, for other
             * reasons, still need the "length in bytes in the field",
             * especially for FT_STRINGZ.)
             *
             * This is true even for ASCII and UTF-8, because
             * substituting REPLACEMENT CHARACTERS for illegal characters
             * can also do so (and for UTF-8 possibly even make the
             * string _shorter_).
             */
        detect_trailing_stray_characters(encoding, stringval, length, pi);
        break;
    default:
        break;
    }

    return pi;
}

proto_item *proto_tree_add_item_ret_int(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                            const int start, int length,
                            const unsigned encoding, int32_t *retval)
{
    hf_info *hfinfo;
    field_info      *new_fi;
    int32_t           value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_INT8:
    case FT_INT16:
    case FT_INT24:
    case FT_INT32:
        break;
    case FT_INT64:
        REPORT_DISSECTOR_BUG("64-bit signed integer field %s used with proto_tree_add_item_ret_int()",
            hfinfo->abbrev);
        break;
    default:
        REPORT_DISSECTOR_BUG("Non-signed-integer field %s used with proto_tree_add_item_ret_int()",
            hfinfo->abbrev);
        break;
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = 0;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    value = get_int_value(tree, tvb, start, length, encoding);

    if (retval) {
        int no_of_bits;
        *retval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            *retval &= (uint32_t)(hfinfo->bitmask);
            /* Shift bits */
            *retval >>= hfinfo_bitshift(hfinfo);
        }
        no_of_bits = ws_count_ones(hfinfo->bitmask);
        *retval = ws_sign_ext32(*retval, no_of_bits);
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_int(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_uint(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                             const int start, int length,
                             const unsigned encoding, uint32_t *retval)
{
    hf_info *hfinfo;
    field_info      *new_fi;
    uint32_t           value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_CHAR:
    case FT_UINT8:
    case FT_UINT16:
    case FT_UINT24:
    case FT_UINT32:
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_CHAR, FT_UINT8, FT_UINT16, FT_UINT24, or FT_UINT32",
            hfinfo->abbrev);
        break;
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = 0;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    /* XXX - modify if we ever support EBCDIC FT_CHAR */
    if (encoding & ENC_VARINT_MASK) {
        uint64_t temp64;
        tvb_get_varint(tvb, start, length, &temp64, encoding);
        value = (uint32_t)temp64;
    } else {
        value = get_uint_value(tree, tvb, start, length, encoding);
    }

    if (retval) {
        *retval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            *retval &= (uint32_t)(hfinfo->bitmask);
            /* Shift bits */
            *retval >>= hfinfo_bitshift(hfinfo);
        }
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_uint(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;
    if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
        new_fi->flags |= FI_VARINT;
    }
    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_uint64(proto_tree *tree, int hfindex, tvbuff_t *tvb,
    const int start, int length, const unsigned encoding, uint64_t *retval)
{
    hf_info *hfinfo;
    field_info      *new_fi;
    uint64_t           value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_UINT40:
    case FT_UINT48:
    case FT_UINT56:
    case FT_UINT64:
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_UINT40, FT_UINT48, FT_UINT56, or FT_UINT64",
            hfinfo->abbrev);
        break;
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = 0;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    if (encoding & ENC_VARINT_MASK) {
        tvb_get_varint(tvb, start, length, &value, encoding);
    } else {
        value = get_uint64_value(tree, tvb, start, length, encoding);
    }

    if (retval) {
        *retval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            *retval &= hfinfo->bitmask;
            /* Shift bits */
            *retval >>= hfinfo_bitshift(hfinfo);
        }
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_uint64(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;
    if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
        new_fi->flags |= FI_VARINT;
    }

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_int64(proto_tree *tree, int hfindex, tvbuff_t *tvb,
    const int start, int length, const unsigned encoding, int64_t *retval)
{
    hf_info *hfinfo;
    field_info      *new_fi;
    int64_t           value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_INT40:
    case FT_INT48:
    case FT_INT56:
    case FT_INT64:
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_INT40, FT_INT48, FT_INT56, or FT_INT64",
            hfinfo->abbrev);
        break;
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = 0;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    if (encoding & ENC_VARINT_MASK) {
        tvb_get_varint(tvb, start, length, &value, encoding);
    }
    else {
        value = get_int64_value(tree, tvb, start, length, encoding);
    }

    if (retval) {
        *retval = value;
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_int64(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;
    if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
        new_fi->flags |= FI_VARINT;
    }

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_varint(proto_tree *tree, int hfindex, tvbuff_t *tvb,
    const int start, int length, const unsigned encoding, uint64_t *retval, int *lenretval)
{
    hf_info *hfinfo;
    field_info    *new_fi;
    uint64_t        value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    if ((!FT_IS_INT(hfinfo->type)) && (!FT_IS_UINT(hfinfo->type))) {
        REPORT_DISSECTOR_BUG("field %s is not of type FT_UINT or FT_INT",
            hfinfo->abbrev);
    }

    /* length validation for native number encoding caught by get_uint64_value() */
    /* length has to be -1 or > 0 regardless of encoding */
    if (length == 0)
        REPORT_DISSECTOR_BUG("Invalid length %d passed to proto_tree_add_item_ret_varint",
            length);

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }

    length = tvb_get_varint(tvb, start, (length == -1) ? FT_VARINT_MAX_LEN : length, &value, encoding);

    if (retval) {
        *retval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            *retval &= hfinfo->bitmask;
            /* Shift bits */
            *retval >>= hfinfo_bitshift(hfinfo);
        }
    }

    if (lenretval) {
        *lenretval = length;
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_uint64(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;
    if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
        new_fi->flags |= FI_VARINT;
    }

    return proto_tree_add_node(tree, new_fi);

}

proto_item *proto_tree_add_item_ret_boolean(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                                const int start, int length,
                                const unsigned encoding, bool *retval)
{
    hf_info *hfinfo;
    field_info      *new_fi;
    uint64_t           value, bitval;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    if (hfinfo->type != FT_BOOLEAN) {
        REPORT_DISSECTOR_BUG("field %s is not of type FT_BOOLEAN",
            hfinfo->abbrev);
    }

    CHECK_FOR_ZERO_OR_MINUS_LENGTH_AND_CLEANUP(length,
        {
            if(retval)
            {
                *retval = false;
            }
        } );

    if (encoding & ENC_STRING) {
        REPORT_DISSECTOR_BUG("wrong encoding");
    }
    /* I believe it's ok if this is called with a NULL tree */
    value = get_uint64_value(tree, tvb, start, length, encoding);

    if (retval) {
        bitval = value;
        if (hfinfo->bitmask) {
            /* Mask out irrelevant portions */
            bitval &= hfinfo->bitmask;
        }
        *retval = (bitval != 0);
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);

    proto_tree_set_boolean(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_float(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                                const int start, int length,
                                const unsigned encoding, float *retval)
{
    hf_info *hfinfo = proto_registrar_get_nth(hfindex);
    field_info      *new_fi;
    float           value;

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    if (hfinfo->type != FT_FLOAT) {
        REPORT_DISSECTOR_BUG("field %s is not of type FT_FLOAT", hfinfo->abbrev);
    }

    if (length != 4) {
        report_type_length_mismatch(tree, "a single-precision floating point number", length, true);
    }

    /* treat any nonzero encoding as little endian for backwards compatibility */
    value = encoding ? tvb_get_letohieee_float(tvb, start) : tvb_get_ntohieee_float(tvb, start);
    if (retval) {
        *retval = value;
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);
    if (encoding) {
        new_fi->flags |= FI_LITTLE_ENDIAN;
    }

    proto_tree_set_float(new_fi, value);

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_double(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                                const int start, int length,
                                const unsigned encoding, double *retval)
{
    hf_info *hfinfo = proto_registrar_get_nth(hfindex);
    field_info      *new_fi;
    double           value;

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    if (hfinfo->type != FT_DOUBLE) {
        REPORT_DISSECTOR_BUG("field %s is not of type FT_DOUBLE", hfinfo->abbrev);
    }

    if (length != 8) {
        report_type_length_mismatch(tree, "a double-precision floating point number", length, true);
    }

    /* treat any nonzero encoding as little endian for backwards compatibility */
    value = encoding ? tvb_get_letohieee_double(tvb, start) : tvb_get_ntohieee_double(tvb, start);
    if (retval) {
        *retval = value;
    }

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, length);
    if (encoding) {
        new_fi->flags |= FI_LITTLE_ENDIAN;
    }

    proto_tree_set_double(new_fi, value);

    return proto_tree_add_node(tree, new_fi);
}

proto_item *proto_tree_add_item_ret_string_and_length(proto_tree *tree, int hfindex,
                                          tvbuff_t *tvb,
                                          const int start, int length,
                                          const unsigned encoding,
                                          const uint8_t **retval,
                                          int *lenretval)
{
    proto_item *pi;
    hf_info *hfinfo;
    field_info      *new_fi;
    const uint8_t      *value;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_STRING:
        value = get_string_value(tvb, start, length, lenretval, encoding);
        break;
    case FT_STRINGZ:
        value = get_stringz_value(tree, tvb, start, length, lenretval, encoding);
        break;
    case FT_UINT_STRING:
        value = get_uint_string_value(tree, tvb, start, length, lenretval, encoding);
        break;
//    case FT_STRINGZPAD:
//        value = get_stringzpad_value(tvb, start, length, lenretval, encoding);
//        break;
//    case FT_STRINGZTRUNC:
//        value = get_stringztrunc_value(tvb, start, length, lenretval, encoding);
//        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_STRING, FT_STRINGZ, FT_UINT_STRING, FT_STRINGZPAD, or FT_STRINGZTRUNC",
            hfinfo->abbrev);
        break;
    }

    if (retval)
        *retval = value;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, *lenretval);

    proto_tree_set_string(new_fi, value);

    new_fi->flags |= (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN;

    pi = proto_tree_add_node(tree, new_fi);

    switch (hfinfo->type) {
    case FT_STRINGZ:
    case FT_STRINGZPAD:
    case FT_STRINGZTRUNC:
    case FT_UINT_STRING:
        break;
    case FT_STRING:
        detect_trailing_stray_characters(encoding, value, length, pi);
        break;
    default:
        break;
    }

    return pi;
}

proto_item *proto_tree_add_item_ret_string(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                               const int start, int length,
                               const unsigned encoding,
                               const uint8_t **retval)
{
    return proto_tree_add_item_ret_string_and_length(tree, hfindex,
        tvb, start, length, encoding, retval, &length);
}

/* Add an item to a proto_tree, using the text label registered to that item;
   the item is extracted from the tvbuff handed to it. */
proto_item *proto_tree_add_item_new(proto_tree *tree, hf_info *hfinfo, tvbuff_t *tvb,
            const int start, int length, const unsigned encoding)
{
    field_info        *new_fi;
    int          item_length;

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    get_hfi_length(hfinfo, tvb, start, &length, &item_length, encoding);
    test_length(hfinfo, tvb, start, item_length, encoding);

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo);

    new_fi = new_field_info(tree, hfinfo, tvb, start, item_length);

    return proto_tree_new_item(new_fi, tree, tvb, start, length, encoding);
}

proto_item *proto_tree_add_item(proto_tree *tree, int hfindex, tvbuff_t *tvb,
            const int start, int length, const unsigned encoding)
{
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    return proto_tree_add_item_new(tree, hfinfo, tvb, start, length, encoding);
}

/* Add an item to a proto_tree, using the text label registered to that item;
   the item is extracted from the tvbuff handed to it.

   Return the length of the item through the pointer. */
proto_item *proto_tree_add_item_new_ret_length(proto_tree *tree, hf_info *hfinfo,
                   tvbuff_t *tvb, const int start,
                   int length, const unsigned encoding,
                   int *lenretval)
{
    field_info        *new_fi;
    int          item_length;
    proto_item     *item;

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    get_hfi_length(hfinfo, tvb, start, &length, &item_length, encoding);
    test_length(hfinfo, tvb, start, item_length, encoding);

    if (!tree) {
        /*
         * We need to get the correct item length here.
         * That's normally done by proto_tree_new_item(),
         * but we won't be calling it.
         */
        *lenretval = get_full_length(hfinfo, tvb, start, length,
            item_length, encoding);
        return NULL;
    }

//    TRY_TO_FAKE_THIS_ITEM_OR_FREE(tree, hfinfo->id, hfinfo, {
//        /*
//         * Even if the tree item is not referenced (and thus faked),
//         * the caller must still be informed of the actual length.
//         */
//        *lenretval = get_full_length(hfinfo, tvb, start, length,
//            item_length, encoding);
//    });

    new_fi = new_field_info(tree, hfinfo, tvb, start, item_length);

    item = proto_tree_new_item(new_fi, tree, tvb, start, length, encoding);
    *lenretval = new_fi->length;
    return item;
}

proto_item *proto_tree_add_item_ret_length(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   const int start, int length,
                   const unsigned encoding, int *lenretval)
{
    register hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    return proto_tree_add_item_new_ret_length(tree, hfinfo, tvb, start, length, encoding, lenretval);
}

/* which FT_ types can use proto_tree_add_bytes_item() */
static inline bool validate_proto_tree_add_bytes_ftype(const enum ftenum type)
{
    return (type == FT_BYTES      ||
        type == FT_UINT_BYTES ||
        type == FT_OID        ||
        type == FT_REL_OID    ||
        type == FT_SYSTEM_ID  );
}

/* Note: this does no validation that the byte array of an FT_OID or
   FT_REL_OID is actually valid; and neither does proto_tree_add_item(),
   so I think it's ok to continue not validating it?
 */
proto_item *proto_tree_add_bytes_item(proto_tree *tree, int hfindex, tvbuff_t *tvb,
               const int start, int length, const unsigned encoding,
               GByteArray *retval, int *endoff, int *err)
{
    field_info      *new_fi;
    GByteArray      *bytes = retval;
    GByteArray      *created_bytes = NULL;
    bool       failed = false;
    uint32_t           n = 0;
    hf_info *hfinfo;
    bool       generate = (bytes || tree) ? true : false;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);

    DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!");

    DISSECTOR_ASSERT_HINT(validate_proto_tree_add_bytes_ftype(hfinfo->type),
        "Called proto_tree_add_bytes_item but not a bytes-based FT_XXX type");

    CHECK_FOR_ZERO_OR_MINUS_LENGTH(length);

    if (encoding & ENC_STR_NUM) {
        REPORT_DISSECTOR_BUG("Decoding number strings for byte arrays is not supported");
    }

    if (generate && (encoding & ENC_STR_HEX)) {
        if (hfinfo->type == FT_UINT_BYTES) {
            /* can't decode FT_UINT_BYTES from strings */
            REPORT_DISSECTOR_BUG("proto_tree_add_bytes_item called for "
                "FT_UINT_BYTES type, but as ENC_STR_HEX");
        }

        if (!bytes) {
            /* caller doesn't care about return value, but we need it to
               call tvb_get_string_bytes() and set the tree later */
            bytes = created_bytes = g_byte_array_new();
        }

        /*
         * bytes might be NULL after this, but can't add expert
         * error until later; if it's NULL, just note that
         * it failed.
         */
        bytes = tvb_get_string_bytes(tvb, start, length, encoding, bytes, endoff);
        if (bytes == NULL)
            failed = true;
    }
    else if (generate) {
        tvb_ensure_bytes_exist(tvb, start, length);

        if (hfinfo->type == FT_UINT_BYTES) {
            n = length; /* n is now the "header" length */
            length = get_uint_value(tree, tvb, start, n, encoding);
            /* length is now the value's length; only store the value in the array */
            tvb_ensure_bytes_exist(tvb, start + n, length);
            if (!bytes) {
                /* caller doesn't care about return value, but
                 * we may need it to set the tree later */
                bytes = created_bytes = g_byte_array_new();
            }
            g_byte_array_append(bytes, tvb_get_ptr(tvb, start + n, length), length);
        }
        else if (length > 0) {
            if (!bytes) {
                /* caller doesn't care about return value, but
                 * we may need it to set the tree later */
                bytes = created_bytes = g_byte_array_new();
            }
            g_byte_array_append(bytes, tvb_get_ptr(tvb, start, length), length);
        }

        if (endoff)
            *endoff = start + n + length;
    }

    if (err)
        *err = failed ? EINVAL : 0;

    CHECK_FOR_NULL_TREE_AND_FREE(tree,
        {
            if (created_bytes)
            g_byte_array_free(created_bytes, true);
            created_bytes = NULL;
            bytes = NULL;
        } );

    TRY_TO_FAKE_THIS_ITEM_OR_FREE(tree, hfinfo->id, hfinfo,
        {
            if (created_bytes)
            g_byte_array_free(created_bytes, true);
            created_bytes = NULL;
            bytes = NULL;
        } );

    /* n will be zero except when it's a FT_UINT_BYTES */
    new_fi = new_field_info(tree, hfinfo, tvb, start, n + length);

    if (encoding & ENC_STRING) {
//        if (failed)
//            expert_add_info(NULL, tree, &ei_byte_array_string_decoding_failed_error);

        if (bytes)
            proto_tree_set_bytes_gbytearray(new_fi, bytes);
        else
            proto_tree_set_bytes(new_fi, NULL, 0);

        if (created_bytes)
            g_byte_array_free(created_bytes, true);
    }
    else {
        /* n will be zero except when it's a FT_UINT_BYTES */
        proto_tree_set_bytes_tvb(new_fi, tvb, start + n, length);

        /* XXX: If we have a non-NULL tree but NULL retval, we don't
         * use the byte array created above in this case.
         */
        if (created_bytes)
            g_byte_array_free(created_bytes, true);

        FI_SET_FLAG(new_fi,
            (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN);
    }

    return proto_tree_add_node(tree, new_fi);
}

/* Add a FT_NONE to a proto_tree */
proto_item *proto_tree_add_none_format(proto_tree *tree, const int hfindex, tvbuff_t *tvb,
               const int start, int length, const char *format,
               ...)
{
    proto_item      *pi;
    va_list           ap;
    hf_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_NONE);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);

    TRY_TO_FAKE_THIS_REPR(pi);

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);

    /* no value to set for FT_NONE */
    return pi;
}

static void proto_tree_set_protocol_tvb(field_info *fi, tvbuff_t *tvb, const char* field_data, int length)
{
    fvalue_set_protocol(fi->value, tvb, field_data, length);
}

/* Add a FT_PROTOCOL to a proto_tree */
proto_item *proto_tree_add_protocol_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   int start, int length, const char *format, ...)
{
    proto_item      *pi;
    tvbuff_t      *protocol_tvb;
    va_list           ap;
    hf_info *hfinfo;
    char* protocol_rep;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_PROTOCOL);

    /*
     * This can throw an exception, so do it before we allocate anything.
     */
    protocol_tvb = (start == 0 ? tvb : tvb_new_subset_length(tvb, start, length));

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);

    va_start(ap, format);
    protocol_rep = strdup_vprintf(format, ap);
    proto_tree_set_protocol_tvb(PNODE_FINFO(pi), protocol_tvb, protocol_rep, length);
    g_free(protocol_rep);
    va_end(ap);

    TRY_TO_FAKE_THIS_REPR(pi);

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);

    return pi;
}

/* Add a FT_BYTES to a proto_tree */
proto_item *proto_tree_add_bytes(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
             int length, const uint8_t *start_ptr)
{
    proto_item      *pi;
    hf_info *hfinfo;
    int          item_length;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    get_hfi_length(hfinfo, tvb, start, &length, &item_length, ENC_NA);
    test_length(hfinfo, tvb, start, item_length, ENC_NA);

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_BYTES);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    proto_tree_set_bytes(PNODE_FINFO(pi), start_ptr, length);

    return pi;
}

/* Add a FT_BYTES to a proto_tree */
proto_item *proto_tree_add_bytes_with_length(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
             int tvbuff_length, const uint8_t *start_ptr, int ptr_length)
{
    proto_item    *pi;
    hf_info *hfinfo;
    int           item_length;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    get_hfi_length(hfinfo, tvb, start, &tvbuff_length, &item_length, ENC_NA);
    test_length(hfinfo, tvb, start, item_length, ENC_NA);

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_BYTES);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &tvbuff_length);
    proto_tree_set_bytes(PNODE_FINFO(pi), start_ptr, ptr_length);

    return pi;
}

proto_item *proto_tree_add_bytes_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                  int start, int length,
                  const uint8_t *start_ptr,
                  const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    if (start_ptr == NULL)
        start_ptr = tvb_get_ptr(tvb, start, length);

    pi = proto_tree_add_bytes(tree, hfindex, tvb, start, length, start_ptr);

    TRY_TO_FAKE_THIS_REPR_NESTED(pi);

    va_start(ap, format);
    proto_tree_set_representation_value(pi, format, ap);
    va_end(ap);

    return pi;
}

proto_item *proto_tree_add_bytes_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                int start, int length, const uint8_t *start_ptr,
                const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    if (start_ptr == NULL)
        start_ptr = tvb_get_ptr(tvb, start, length);

    pi = proto_tree_add_bytes(tree, hfindex, tvb, start, length, start_ptr);

    TRY_TO_FAKE_THIS_REPR_NESTED(pi);

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);

    return pi;
}

static void
proto_tree_set_bytes(field_info *fi, const uint8_t* start_ptr, int length)
{
    DISSECTOR_ASSERT(length >= 0);
    DISSECTOR_ASSERT(start_ptr != NULL || length == 0);

    fvalue_set_bytes_data(fi->value, start_ptr, length);
}


static void
proto_tree_set_bytes_tvb(field_info *fi, tvbuff_t *tvb, int offset, int length)
{
    tvb_ensure_bytes_exist(tvb, offset, length);
    proto_tree_set_bytes(fi, tvb_get_ptr(tvb, offset, length), length);
}

static void proto_tree_set_bytes_gbytearray(field_info *fi, const GByteArray *value)
{
    GByteArray *bytes;

    DISSECTOR_ASSERT(value != NULL);

    bytes = byte_array_dup(value);

    fvalue_set_byte_array(fi->value, bytes);
}

/* Add a FT_STRING, FT_STRINGZ, FT_STRINGZPAD, or FT_STRINGZTRUNC to a
 * proto_tree. Creates own copy of string, and frees it when the proto_tree
 * is destroyed. */
proto_item *proto_tree_add_string(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
              int length, const char* value)
{
    proto_item      *pi;
    hf_info *hfinfo;
    int          item_length;

    PROTO_REGISTRAR_GET_NTH(hfindex, hfinfo);
    get_hfi_length(hfinfo, tvb, start, &length, &item_length, ENC_NA);
    /*
     * Special case - if the length is 0, skip the test, so that
     * we can have an empty string right after the end of the
     * packet.  (This handles URL-encoded forms where the last field
     * has no value so the form ends right after the =.)
     */
    if (item_length != 0)
        test_length(hfinfo, tvb, start, item_length, ENC_NA);

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE_IS_STRING(hfinfo);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    DISSECTOR_ASSERT(length >= 0);

//    WS_UTF_8_CHECK(value, -1);
    proto_tree_set_string(PNODE_FINFO(pi), value);

    return pi;
}

proto_item *proto_tree_add_string_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   int start, int length, const char* value,
                   const char *format,
                   ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_string(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_string_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                 int start, int length, const char* value,
                 const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_string(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_STRING value */
static void proto_tree_set_string(field_info *fi, const char* value)
{
    if (value) {
        fvalue_set_string(fi->value, value);
    } else {
        /*
         * XXX - why is a null value for a string field
         * considered valid?
         */
        fvalue_set_string(fi->value, "[ Null ]");
    }
}

/* Add a FT_BOOLEAN to a proto_tree */
proto_item *proto_tree_add_boolean(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
               int length, uint64_t value)
{
    proto_item      *pi;
    hf_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_BOOLEAN);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    proto_tree_set_boolean(PNODE_FINFO(pi), value);

    return pi;
}

proto_item *proto_tree_add_boolean_format_value(proto_tree *tree, int hfindex,
                    tvbuff_t *tvb, int start, int length,
                    uint64_t value, const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_boolean(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_boolean_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                  int start, int length, uint64_t value,
                  const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_boolean(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_BOOLEAN value */
static void proto_tree_set_boolean(field_info *fi, uint64_t value)
{
    proto_tree_set_uint64(fi, value);
}

/* Generate, into "buf", a string showing the bits of a bitfield.
   Return a pointer to the character after that string. */
static char *other_decode_bitfield_value(char *buf, const uint64_t val, const uint64_t mask, const int width)
{
    int i = 0;
    uint64_t bit;
    char *p;

    p = buf;

    /* This is a devel error. It is safer to stop here. */
    DISSECTOR_ASSERT(width >= 1);

    bit = UINT64_C(1) << (width - 1);
    for (;;) {
        if (mask & bit) {
            /* This bit is part of the field.  Show its value. */
            if (val & bit)
                *p++ = '1';
            else
                *p++ = '0';
        } else {
            /* This bit is not part of the field. */
            *p++ = '.';
        }
        bit >>= 1;
        i++;
        if (i >= width)
            break;
        if (i % 4 == 0)
            *p++ = ' ';
    }
    *p = '\0';
    return p;
}

static char *decode_bitfield_value(char *buf, const uint64_t val, const uint64_t mask, const int width)
{
    char *p;

    p = other_decode_bitfield_value(buf, val, mask, width);
    p = g_stpcpy(p, " = ");

    return p;
}

static char *other_decode_bitfield_varint_value(char *buf, uint64_t val, uint64_t mask, const int width)
{
    int i = 0;
    uint64_t bit;
    char *p;

    p = buf;

    /* This is a devel error. It is safer to stop here. */
    DISSECTOR_ASSERT(width >= 1);

    bit = UINT64_C(1) << (width - 1);
    for (;;) {
        if (((8-(i % 8)) != 8) && /* MSB is never used for value. */
            (mask & bit)) {
            /* This bit is part of the field.  Show its value. */
            if (val & bit)
                *p++ = '1';
            else
                *p++ = '0';
        } else {
            /* This bit is not part of the field. */
            *p++ = '.';
        }
        bit >>= 1;
        i++;
        if (i >= width)
            break;
        if (i % 4 == 0)
            *p++ = ' ';
    }

    *p = '\0';
    return p;
}

static char *decode_bitfield_varint_value(char *buf, const uint64_t val, const uint64_t mask, const int width)
{
    char *p;

    p = other_decode_bitfield_varint_value(buf, val, mask, width);
    p = g_stpcpy(p, " = ");

    return p;
}

/* Add a FT_FLOAT to a proto_tree */
proto_item *proto_tree_add_float(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
             int length, float value)
{
    proto_item      *pi;
    hf_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_FLOAT);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    proto_tree_set_float(PNODE_FINFO(pi), value);

    return pi;
}

proto_item *proto_tree_add_float_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                  int start, int length, float value,
                  const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_float(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_float_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                int start, int length, float value,
                const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_float(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_FLOAT value */
static void proto_tree_set_float(field_info *fi, float value)
{
    fvalue_set_floating(fi->value, value);
}

/* Add a FT_DOUBLE to a proto_tree */
proto_item *proto_tree_add_double(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
              int length, double value)
{
    proto_item      *pi;
    hf_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    DISSECTOR_ASSERT_FIELD_TYPE(hfinfo, FT_DOUBLE);

    pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
    proto_tree_set_double(PNODE_FINFO(pi), value);

    return pi;
}

proto_item *proto_tree_add_double_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   int start, int length, double value,
                   const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_double(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_double_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                 int start, int length, double value,
                 const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_double(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_DOUBLE value */
static void proto_tree_set_double(field_info *fi, double value)
{
    fvalue_set_floating(fi->value, value);
}

/* Add FT_CHAR or FT_UINT{8,16,24,32} to a proto_tree */
proto_item *proto_tree_add_uint(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
            int length, uint32_t value)
{
    proto_item      *pi = NULL;
    hf_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_CHAR:
    case FT_UINT8:
    case FT_UINT16:
    case FT_UINT24:
    case FT_UINT32:
    case FT_FRAMENUM:
        pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
        proto_tree_set_uint(PNODE_FINFO(pi), value);
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_CHAR, FT_UINT8, FT_UINT16, FT_UINT24, FT_UINT32, or FT_FRAMENUM",
            hfinfo->abbrev);
        break;
    }

    return pi;
}

proto_item *proto_tree_add_uint_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                 int start, int length, uint32_t value,
                 const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_uint(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_uint_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
               int start, int length, uint32_t value,
               const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_uint(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_UINT{8,16,24,32} value */
static void proto_tree_set_uint(field_info *fi, uint32_t value)
{
    const hf_info *hfinfo;
    uint32_t           integer;

    hfinfo = fi->hfinfo;
    integer = value;

    if (hfinfo->bitmask) {
        /* Mask out irrelevant portions */
        integer &= (uint32_t)(hfinfo->bitmask);

        /* Shift bits */
        integer >>= hfinfo_bitshift(hfinfo);

        FI_SET_FLAG(fi, FI_BITS_OFFSET(hfinfo_bitoffset(hfinfo)));
        FI_SET_FLAG(fi, FI_BITS_SIZE(hfinfo_mask_bitwidth(hfinfo)));
    }

    fvalue_set_uinteger(fi->value, integer);
}

/* Add FT_UINT{40,48,56,64} to a proto_tree */
proto_item *proto_tree_add_uint64(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
              int length, uint64_t value)
{
    proto_item      *pi = NULL;
    hf_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_UINT40:
    case FT_UINT48:
    case FT_UINT56:
    case FT_UINT64:
    case FT_FRAMENUM:
        pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
        proto_tree_set_uint64(PNODE_FINFO(pi), value);
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_UINT40, FT_UINT48, FT_UINT56, FT_UINT64, or FT_FRAMENUM",
            hfinfo->abbrev);
        break;
    }

    return pi;
}

proto_item *proto_tree_add_uint64_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                   int start, int length, uint64_t value,
                   const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_uint64(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_uint64_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                 int start, int length, uint64_t value,
                 const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_uint64(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_UINT{40,48,56,64} value */
static void proto_tree_set_uint64(field_info *fi, uint64_t value)
{
    const hf_info *hfinfo;
    uint64_t           integer;

    hfinfo = fi->hfinfo;
    integer = value;

    if (hfinfo->bitmask) {
        /* Mask out irrelevant portions */
        integer &= hfinfo->bitmask;

        /* Shift bits */
        integer >>= hfinfo_bitshift(hfinfo);

        FI_SET_FLAG(fi, FI_BITS_OFFSET(hfinfo_bitoffset(hfinfo)));
        FI_SET_FLAG(fi, FI_BITS_SIZE(hfinfo_mask_bitwidth(hfinfo)));
    }

    fvalue_set_uinteger64(fi->value, integer);
}

/* Add FT_INT{8,16,24,32} to a proto_tree */
proto_item *proto_tree_add_int(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
           int length, int32_t value)
{
    proto_item      *pi = NULL;
    hf_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    switch (hfinfo->type) {
    case FT_INT8:
    case FT_INT16:
    case FT_INT24:
    case FT_INT32:
        pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
        proto_tree_set_int(PNODE_FINFO(pi), value);
        break;
    default:
        REPORT_DISSECTOR_BUG("field %s is not of type FT_INT8, FT_INT16, FT_INT24, or FT_INT32",
            hfinfo->abbrev);
    break;
    }

    return pi;
}

proto_item *proto_tree_add_int_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                int start, int length, int32_t value,
                const char *format, ...)
{
    proto_item  *pi;
    va_list         ap;

    pi = proto_tree_add_int(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

proto_item *proto_tree_add_int_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
              int start, int length, int32_t value,
              const char *format, ...)
{
    proto_item *pi;
    va_list     ap;

    pi = proto_tree_add_int(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_INT{8,16,24,32} value */
static void proto_tree_set_int(field_info *fi, int32_t value)
{
    const hf_info *hfinfo;
    uint32_t           integer;
    int           no_of_bits;

    hfinfo = fi->hfinfo;
    integer = (uint32_t) value;

    if (hfinfo->bitmask) {
        /* Mask out irrelevant portions */
        integer &= (uint32_t)(hfinfo->bitmask);

        /* Shift bits */
        integer >>= hfinfo_bitshift(hfinfo);

        no_of_bits = ws_count_ones(hfinfo->bitmask);
        integer = ws_sign_ext32(integer, no_of_bits);

        FI_SET_FLAG(fi, FI_BITS_OFFSET(hfinfo_bitoffset(hfinfo)));
        FI_SET_FLAG(fi, FI_BITS_SIZE(hfinfo_mask_bitwidth(hfinfo)));
    }

    fvalue_set_sinteger(fi->value, integer);
}

/* Add FT_INT{40,48,56,64} to a proto_tree */
proto_item *proto_tree_add_int64(proto_tree *tree, int hfindex, tvbuff_t *tvb, int start,
             int length, int64_t value)
{
    proto_item      *pi = NULL;
    hf_info *hfinfo;

    CHECK_FOR_NULL_TREE(tree);

    TRY_TO_FAKE_THIS_ITEM(tree, hfindex, hfinfo);

    switch (hfinfo->type) {
        case FT_INT40:
        case FT_INT48:
        case FT_INT56:
        case FT_INT64:
            pi = proto_tree_add_pi(tree, hfinfo, tvb, start, &length);
            proto_tree_set_int64(PNODE_FINFO(pi), value);
            break;

        default:
            REPORT_DISSECTOR_BUG("field %s is not of type FT_INT40, FT_INT48, FT_INT56, or FT_INT64",
                hfinfo->abbrev);
    }

    return pi;
}

proto_item *proto_tree_add_int64_format_value(proto_tree *tree, int hfindex, tvbuff_t *tvb,
                  int start, int length, int64_t value,
                  const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_int64(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        va_start(ap, format);
        proto_tree_set_representation_value(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Set the FT_INT{40,48,56,64} value */
static void proto_tree_set_int64(field_info *fi, int64_t value)
{
    const hf_info *hfinfo;
    uint64_t           integer;
    int           no_of_bits;

    hfinfo = fi->hfinfo;
    integer = value;

    if (hfinfo->bitmask) {
        /* Mask out irrelevant portions */
        integer &= hfinfo->bitmask;

        /* Shift bits */
        integer >>= hfinfo_bitshift(hfinfo);

        no_of_bits = ws_count_ones(hfinfo->bitmask);
        integer = ws_sign_ext64(integer, no_of_bits);

        FI_SET_FLAG(fi, FI_BITS_OFFSET(hfinfo_bitoffset(hfinfo)));
        FI_SET_FLAG(fi, FI_BITS_SIZE(hfinfo_mask_bitwidth(hfinfo)));
    }

    fvalue_set_sinteger64(fi->value, integer);
}

proto_item *proto_tree_add_int64_format(proto_tree *tree, int hfindex, tvbuff_t *tvb,
               int start, int length, int64_t value,
               const char *format, ...)
{
    proto_item      *pi;
    va_list           ap;

    pi = proto_tree_add_int64(tree, hfindex, tvb, start, length, value);
    if (pi != tree) {
        TRY_TO_FAKE_THIS_REPR(pi);

        va_start(ap, format);
        proto_tree_set_representation(pi, format, ap);
        va_end(ap);
    }

    return pi;
}

/* Add a field_info struct to the proto_tree, encapsulating it in a proto_node */
static proto_item *proto_tree_add_node(proto_tree *tree, field_info *fi)
{
    proto_node *pnode, *tnode, *sibling;
    field_info *tfi;
    unsigned depth = 1;

    /*
     * Restrict our depth. proto_tree_traverse_pre_order and
     * proto_tree_traverse_post_order (and possibly others) are recursive
     * so we need to be mindful of our stack size.
     */
    if (tree->first_child == NULL) {
        for (tnode = tree; tnode != NULL; tnode = tnode->parent) {
            depth++;
            // TODO: FIX max_tree_depth
            if (depth > 32) {
                fvalue_free(fi->value);
                fi->value = NULL;
            }
//            if (G_UNLIKELY(depth > prefs.gui_max_tree_depth)) {
//                fvalue_free(fi->value);
//                fi->value = NULL;
//                THROW_MESSAGE(DissectorError, wmem_strdup_printf(
//                             "Maximum tree depth %d exceeded for \"%s\" - \"%s\" (%s:%u) (Maximum depth can be increased in advanced preferences)",
//                             prefs.gui_max_tree_depth,
//                             fi->hfinfo->name, fi->hfinfo->abbrev, G_STRFUNC, __LINE__));
//            }
        }
    }

    /*
     * Make sure "tree" is ready to have subtrees under it, by
     * checking whether it's been given an ett_ value.
     *
     * "PNODE_FINFO(tnode)" may be null; that's the case for the root
     * node of the protocol tree.  That node is not displayed,
     * so it doesn't need an ett_ value to remember whether it
     * was expanded.
     */
    tnode = tree;
    tfi = PNODE_FINFO(tnode);
    if (tfi != NULL && (tfi->tree_type < 0 || tfi->tree_type >= num_tree_types)) {
        /* Since we are not adding fi to a node, its fvalue won't get
         * freed by proto_tree_free_node(), so free it now.
         */
        fvalue_free(fi->value);
        fi->value = NULL;
        REPORT_DISSECTOR_BUG("\"%s\" - \"%s\" tfi->tree_type: %d invalid (%s:%u)",
                     fi->hfinfo->name, fi->hfinfo->abbrev, tfi->tree_type, __FILE__, __LINE__);
        /* XXX - is it safe to continue here? */
    }

    pnode = wmem_new(proto_node);
    PROTO_NODE_INIT(pnode);
    pnode->parent = tnode;
    PNODE_FINFO(pnode) = fi;
    pnode->tree_data = PTREE_DATA(tree);

    if (tnode->last_child != NULL) {
        sibling = tnode->last_child;
        DISSECTOR_ASSERT(sibling->next == NULL);
        sibling->next = pnode;
    } else
        tnode->first_child = pnode;
    tnode->last_child = pnode;

    tree_data_add_maybe_interesting_field(pnode->tree_data, fi);

    return (proto_item *)pnode;
}

/* Generic way to allocate field_info and add to proto_tree.
 * Sets *pfi to address of newly-allocated field_info struct */
static proto_item *proto_tree_add_pi(proto_tree *tree, hf_info *hfinfo, tvbuff_t *tvb, int start,
          int *length)
{
    proto_item *pi;
    field_info *fi;
    int        item_length;

    get_hfi_length(hfinfo, tvb, start, length, &item_length, ENC_NA);
    fi = new_field_info(tree, hfinfo, tvb, start, item_length);
    pi = proto_tree_add_node(tree, fi);

    return pi;
}


static void get_hfi_length(hf_info *hfinfo, tvbuff_t *tvb, const int start, int *length,
           int *item_length, const unsigned encoding)
{
    int length_remaining;

    /*
     * We only allow a null tvbuff if the item has a zero length,
     * i.e. if there's no data backing it.
     */
    DISSECTOR_ASSERT(tvb != NULL || *length == 0);

    /*
     * XXX - in some protocols, there are 32-bit unsigned length
     * fields, so lengths in protocol tree and tvbuff routines
     * should really be unsigned.  We should have, for those
     * field types for which "to the end of the tvbuff" makes sense,
     * additional routines that take no length argument and
     * add fields that run to the end of the tvbuff.
     */
    if (*length == -1) {
        /*
         * For FT_NONE, FT_PROTOCOL, FT_BYTES, FT_STRING,
         * FT_STRINGZPAD, and FT_STRINGZTRUNC fields, a length
         * of -1 means "set the length to what remains in the
         * tvbuff".
         *
         * The assumption is either that
         *
         *    1) the length of the item can only be determined
         *       by dissection (typically true of items with
         *       subitems, which are probably FT_NONE or
         *       FT_PROTOCOL)
         *
         * or
         *
         *    2) if the tvbuff is "short" (either due to a short
         *       snapshot length or due to lack of reassembly of
         *       fragments/segments/whatever), we want to display
         *       what's available in the field (probably FT_BYTES
         *       or FT_STRING) and then throw an exception later
         *
         * or
         *
         *    3) the field is defined to be "what's left in the
         *       packet"
         *
         * so we set the length to what remains in the tvbuff so
         * that, if we throw an exception while dissecting, it
         * has what is probably the right value.
         *
         * For FT_STRINGZ, it means "the string is null-terminated,
         * not null-padded; set the length to the actual length
         * of the string", and if the tvbuff if short, we just
         * throw an exception.
         *
         * For ENC_VARINT_PROTOBUF|ENC_VARINT_QUIC|ENC_VARIANT_ZIGZAG|ENC_VARINT_SDNV, it means "find the end of the string",
         * and if the tvbuff if short, we just throw an exception.
         *
         * It's not valid for any other type of field.  For those
         * fields, we treat -1 the same way we treat other
         * negative values - we assume the length is a Really
         * Big Positive Number, and throw a ReportedBoundsError
         * exception, under the assumption that the Really Big
         * Length would run past the end of the packet.
         */
        if ((FT_IS_INT(hfinfo->type)) || (FT_IS_UINT(hfinfo->type))) {
            if (encoding & (ENC_VARINT_PROTOBUF|ENC_VARINT_ZIGZAG|ENC_VARINT_SDNV)) {
                /*
                 * Leave the length as -1, so our caller knows
                 * it was -1.
                 */
                *item_length = *length;
                return;
            } else if (encoding & ENC_VARINT_QUIC) {
                switch (tvb_get_uint8(tvb, start) >> 6)
                {
                case 0: /* 0b00 => 1 byte length (6 bits Usable) */
                    *item_length = 1;
                    break;
                case 1: /* 0b01 => 2 bytes length (14 bits Usable) */
                    *item_length = 2;
                    break;
                case 2: /* 0b10 => 4 bytes length (30 bits Usable) */
                    *item_length = 4;
                    break;
                case 3: /* 0b11 => 8 bytes length (62 bits Usable) */
                    *item_length = 8;
                    break;
                }
            }
        }

        switch (hfinfo->type) {

        case FT_PROTOCOL:
        case FT_NONE:
        case FT_BYTES:
        case FT_STRING:
        case FT_STRINGZPAD:
        case FT_STRINGZTRUNC:
            /*
             * We allow FT_PROTOCOLs to be zero-length -
             * for example, an ONC RPC NULL procedure has
             * neither arguments nor reply, so the
             * payload for that protocol is empty.
             *
             * We also allow the others to be zero-length -
             * because that's the way the code has been for a
             * long, long time.
             *
             * However, we want to ensure that the start
             * offset is not *past* the byte past the end
             * of the tvbuff: we throw an exception in that
             * case.
             */
            *length = tvb_captured_length(tvb) ? tvb_ensure_captured_length_remaining(tvb, start) : 0;
            DISSECTOR_ASSERT(*length >= 0);
            break;

        case FT_STRINGZ:
            /*
             * Leave the length as -1, so our caller knows
             * it was -1.
             */
            break;

        default:
//            THROW(ReportedBoundsError);
            DISSECTOR_ASSERT_NOT_REACHED();
            return;
            break;
        }
        *item_length = *length;
    } else {
        *item_length = *length;
        if (hfinfo->type == FT_PROTOCOL || hfinfo->type == FT_NONE) {
            /*
             * These types are for interior nodes of the
             * tree, and don't have data associated with
             * them; if the length is negative (XXX - see
             * above) or goes past the end of the tvbuff,
             * cut it short at the end of the tvbuff.
             * That way, if this field is selected in
             * Wireshark, we don't highlight stuff past
             * the end of the data.
             */
            /* XXX - what to do, if we don't have a tvb? */
            if (tvb) {
                length_remaining = tvb_captured_length_remaining(tvb, start);
                if (*item_length < 0 ||
                    (*item_length > 0 &&
                      (length_remaining < *item_length)))
                    *item_length = length_remaining;
            }
        }
        if (*item_length < 0) {
//            THROW(ReportedBoundsError);
            return;
        }
    }
}

static int get_full_length(hf_info *hfinfo, tvbuff_t *tvb, const int start,
        int length, unsigned item_length, const int encoding)
{
    uint32_t n;

    /*
     * We need to get the correct item length here.
     * That's normally done by proto_tree_new_item(),
     * but we won't be calling it.
     */
    switch (hfinfo->type) {

    case FT_NONE:
    case FT_PROTOCOL:
    case FT_BYTES:
        /*
         * The length is the specified length.
         */
        break;

    case FT_UINT_BYTES:
        n = get_uint_value(NULL, tvb, start, length, encoding);
        item_length += n;
        if ((int)item_length < length) {
//            THROW(ReportedBoundsError);
        }
        break;

    /* XXX - make these just FT_UINT? */
    case FT_UINT8:
    case FT_UINT16:
    case FT_UINT24:
    case FT_UINT32:
    case FT_UINT40:
    case FT_UINT48:
    case FT_UINT56:
    case FT_UINT64:
    /* XXX - make these just FT_INT? */
    case FT_INT8:
    case FT_INT16:
    case FT_INT24:
    case FT_INT32:
    case FT_INT40:
    case FT_INT48:
    case FT_INT56:
    case FT_INT64:
        if (encoding & ENC_VARINT_MASK) {
            if (length < -1) {
                report_type_length_mismatch(NULL, "a FT_[U]INT", length, true);
            }
            if (length == -1) {
                uint64_t dummy;
                /* This can throw an exception */
                /* XXX - do this without fetching the varint? */
                length = tvb_get_varint(tvb, start, FT_VARINT_MAX_LEN, &dummy, encoding);
                if (length == 0) {
//                    THROW(ReportedBoundsError);
                }
            }
            item_length = length;
            break;
        }

        /*
         * The length is the specified length.
         */
        break;

    case FT_BOOLEAN:
    case FT_CHAR:
    case FT_IPv4:
    case FT_IPXNET:
    case FT_IPv6:
    case FT_FCWWN:
    case FT_AX25:
    case FT_VINES:
    case FT_ETHER:
    case FT_EUI64:
    case FT_GUID:
    case FT_OID:
    case FT_REL_OID:
    case FT_SYSTEM_ID:
    case FT_FLOAT:
    case FT_DOUBLE:
    case FT_STRING:
        /*
         * The length is the specified length.
         */
        break;

    case FT_STRINGZ:
        if (length < -1) {
            report_type_length_mismatch(NULL, "a string", length, true);
        }
        if (length == -1) {
            /* This can throw an exception */
            /* XXX - do this without fetching the string? */
            wmem_free(tvb_get_stringz_enc(tvb, start, &length, encoding));
        }
        item_length = length;
        break;

    case FT_UINT_STRING:
        n = get_uint_value(NULL, tvb, start, length, encoding & ~ENC_CHARENCODING_MASK);
        item_length += n;
        if ((int)item_length < length) {
//            THROW(ReportedBoundsError);
        }
        break;

    case FT_STRINGZPAD:
    case FT_STRINGZTRUNC:
    case FT_ABSOLUTE_TIME:
    case FT_RELATIVE_TIME:
    case FT_IEEE_11073_SFLOAT:
    case FT_IEEE_11073_FLOAT:
        /*
         * The length is the specified length.
         */
        break;

    default:
        REPORT_DISSECTOR_BUG("field %s has type %d (%s) not handled in gset_full_length()",
                     hfinfo->abbrev,
                     hfinfo->type,
                     ftype_name(hfinfo->type));
        break;
    }
    return item_length;
}

static field_info *new_field_info(proto_tree *tree, hf_info *hfinfo, tvbuff_t *tvb,
           const int start, const int item_length)
{
    field_info *fi;

    FIELD_INFO_NEW(fi);

    fi->hfinfo     = hfinfo;
    fi->start      = start;
    fi->start     += (tvb) ? tvb_raw_offset(tvb) : 0;
    fi->length     = item_length;
    fi->tree_type  = -1;
    fi->flags      = 0;

    if (!PTREE_DATA(tree)->visible) {
        /* If the tree is not visible, set the item hidden, unless we
         * need the representation or length and can't fake them.
         */
        if (hfinfo->ref_type != HF_REF_TYPE_PRINT && (hfinfo->type != FT_PROTOCOL || PTREE_DATA(tree)->fake_protocols)) {
            FI_SET_FLAG(fi, FI_HIDDEN);
        }
    }

    fi->value = fvalue_new(fi->hfinfo->type);
    fi->rep        = NULL;

    /* add the data source tvbuff */
    fi->ds_tvb = tvb ? tvb_get_ds_tvb(tvb) : NULL;

    fi->appendix_start  = 0;
    fi->appendix_length = 0;

    fi->total_layer_num = tree->tree_data->pinfo->curr_layer_num;
    fi->proto_layer_num = tree->tree_data->pinfo->curr_proto_layer_num;

    return fi;
}

/* If the protocol tree is to be visible, set the representation of a
   proto_tree entry with the name of the field for the item and with
   the value formatted with the supplied printf-style format and
   argument list. */
static void proto_tree_set_representation_value(proto_item *pi, const char *format, va_list ap)
{
    /* If the tree (GUI) or item isn't visible it's pointless for us to generate the protocol
     * items string representation */
    if (PTREE_DATA(pi)->visible && !proto_item_is_hidden(pi)) {
        size_t            name_pos, ret = 0;
        char              *str;
        field_info        *fi = PITEM_FINFO(pi);
        const hf_info *hf;

        DISSECTOR_ASSERT(fi);

        hf = fi->hfinfo;

        ITEM_LABEL_NEW(fi->rep);
        if (hf->bitmask && (hf->type == FT_BOOLEAN || FT_IS_UINT(hf->type))) {
            uint64_t val;
            char *p;

            if (FT_IS_UINT32(hf->type))
                val = fvalue_get_uinteger(fi->value);
            else
                val = fvalue_get_uinteger64(fi->value);

            val <<= hfinfo_bitshift(hf);

            p = decode_bitfield_value(fi->rep->representation, val, hf->bitmask, hfinfo_container_bitwidth(hf));
            ret = (p - fi->rep->representation);
        }

        /* put in the hf name */
        name_pos = ret = label_concat(fi->rep->representation, ret, hf->name);

        ret = label_concat(fi->rep->representation, ret, ": ");
        /* If possible, Put in the value of the string */
        str = strdup_vprintf(format, ap);
//        WS_UTF_8_CHECK(str, -1);
        ret = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, ret, str, 0);
        if (ret >= ITEM_LABEL_LENGTH) {
            /* Uh oh, we don't have enough room.  Tell the user
             * that the field is truncated.
             */
            label_mark_truncated(fi->rep->representation, name_pos);
        }
    }
}

/* If the protocol tree is to be visible, set the representation of a
   proto_tree entry with the representation formatted with the supplied
   printf-style format and argument list. */
static void proto_tree_set_representation(proto_item *pi, const char *format, va_list ap)
{
    size_t        ret;    /*tmp return value */
    char       *str;
    field_info *fi = PITEM_FINFO(pi);

    DISSECTOR_ASSERT(fi);

    if (!proto_item_is_hidden(pi)) {
        ITEM_LABEL_NEW(fi->rep);

        str = strdup_vprintf(format, ap);
//        WS_UTF_8_CHECK(str, -1);
        ret = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, 0, str, 0);
        if (ret >= ITEM_LABEL_LENGTH) {
            /* Uh oh, we don't have enough room.  Tell the user
             * that the field is truncated.
             */
            LABEL_MARK_TRUNCATED_START(fi->rep->representation);
        }
    }
}

static int protol_strlcpy(char *dest, const char *src, size_t dest_size)
{
    if (dest_size == 0) return 0;

    size_t res = g_strlcpy(dest, src, dest_size);

    /* At most dest_size - 1 characters will be copied
     * (unless dest_size is 0). */
    if (res >= dest_size)
        res = dest_size - 1;
    return (int) res;
}

int proto_item_fill_display_label(const field_info *finfo, char *display_label_str, const int label_str_size)
{
    const hf_info *hfinfo = finfo->hfinfo;
    int label_len = 0;
    char *tmp_str;
    const char *str;
    const uint8_t *bytes;
    uint32_t number;
    uint64_t number64;
    const char *hf_str_val;
    char number_buf[NUMBER_LABEL_LENGTH];
    const char *number_out;
//    address addr;
//    const ipv4_addr_and_mask *ipv4;
//    const ipv6_addr_and_prefix *ipv6;

    switch (hfinfo->type) {

        case FT_NONE:
        case FT_PROTOCOL:
            return protol_strlcpy(display_label_str, UTF8_CHECK_MARK, label_str_size);

        case FT_UINT_BYTES:
        case FT_BYTES:
            tmp_str = format_bytes_hfinfo_maxlen(hfinfo,
                fvalue_get_bytes_data(finfo->value),
                (unsigned)fvalue_length2(finfo->value),
                label_str_size);
            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
            wmem_free(tmp_str);
            break;

//        case FT_ABSOLUTE_TIME:
//            tmp_str = abs_time_to_str(NULL, fvalue_get_time(finfo->value), hfinfo->display, true);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(tmp_str);
//            break;

//        case FT_RELATIVE_TIME:
//            tmp_str = rel_time_to_secs_str(NULL, fvalue_get_time(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(tmp_str);
//            break;

        case FT_BOOLEAN:
            number64 = fvalue_get_uinteger64(finfo->value);
            label_len = protol_strlcpy(display_label_str,
                    tfs_get_string(!!number64, hfinfo->strings), label_str_size);
            break;

        case FT_CHAR:
            number = fvalue_get_uinteger(finfo->value);

            if (FIELD_DISPLAY(hfinfo->display) == BASE_CUSTOM) {
                char tmp[ITEM_LABEL_LENGTH];
                custom_fmt_func_t fmtfunc = (custom_fmt_func_t)hfinfo->strings;

                DISSECTOR_ASSERT(fmtfunc);
                fmtfunc(tmp, number);

                label_len = protol_strlcpy(display_label_str, tmp, label_str_size);

            } else if (hfinfo->strings) {
                number_out = hf_try_val_to_str(number, hfinfo);

                if (!number_out) {
                    number_out = hfinfo_char_value_format_display(BASE_HEX, number_buf, number);
                }

                label_len = protol_strlcpy(display_label_str, number_out, label_str_size);

            } else {
                number_out = hfinfo_char_value_format(hfinfo, number_buf, number);

                label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
            }

            break;

        /* XXX - make these just FT_NUMBER? */
        case FT_INT8:
        case FT_INT16:
        case FT_INT24:
        case FT_INT32:
        case FT_UINT8:
        case FT_UINT16:
        case FT_UINT24:
        case FT_UINT32:
        case FT_FRAMENUM:
            hf_str_val = NULL;
            number = FT_IS_INT(hfinfo->type) ?
                (uint32_t)fvalue_get_sinteger(finfo->value) :
                fvalue_get_uinteger(finfo->value);

            if (FIELD_DISPLAY(hfinfo->display) == BASE_CUSTOM) {
                char tmp[ITEM_LABEL_LENGTH];
                custom_fmt_func_t fmtfunc = (custom_fmt_func_t)hfinfo->strings;

                DISSECTOR_ASSERT(fmtfunc);
                fmtfunc(tmp, number);

                label_len = protol_strlcpy(display_label_str, tmp, label_str_size);

            } else if (hfinfo->strings && hfinfo->type != FT_FRAMENUM) {
                if (hfinfo->display & BASE_UNIT_STRING) {
                    number_out = hfinfo_numeric_value_format(hfinfo, number_buf, number);
                    label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
                    hf_str_val = hf_try_val_to_str(number, hfinfo);
                    label_len += protol_strlcpy(display_label_str+label_len, hf_str_val, label_str_size-label_len);
                } else {
                    number_out = hf_try_val_to_str(number, hfinfo);

                    if (!number_out) {
                        number_out = hfinfo_number_value_format_display(hfinfo, hfinfo->display, number_buf, number);
                    }

                    label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
                }
            } else {
                number_out = hfinfo_number_value_format(hfinfo, number_buf, number);

                label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
            }

            break;

        case FT_INT40:
        case FT_INT48:
        case FT_INT56:
        case FT_INT64:
        case FT_UINT40:
        case FT_UINT48:
        case FT_UINT56:
        case FT_UINT64:
            hf_str_val = NULL;
            number64 = FT_IS_INT(hfinfo->type) ?
                (uint64_t)fvalue_get_sinteger64(finfo->value) :
                fvalue_get_uinteger64(finfo->value);

            if (FIELD_DISPLAY(hfinfo->display) == BASE_CUSTOM) {
                char tmp[ITEM_LABEL_LENGTH];
                custom_fmt_func_64_t fmtfunc64 = (custom_fmt_func_64_t)hfinfo->strings;

                DISSECTOR_ASSERT(fmtfunc64);
                fmtfunc64(tmp, number64);

                label_len = protol_strlcpy(display_label_str, tmp, label_str_size);
            } else if (hfinfo->strings) {
                if (hfinfo->display & BASE_UNIT_STRING) {
                    number_out = hfinfo_numeric_value_format64(hfinfo, number_buf, number64);
                    label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
                    hf_str_val = hf_try_val64_to_str(number64, hfinfo);
                    label_len += protol_strlcpy(display_label_str+label_len, hf_str_val, label_str_size-label_len);
                } else {
                    number_out = hf_try_val64_to_str(number64, hfinfo);

                    if (!number_out)
                        number_out = hfinfo_number_value_format_display64(hfinfo, hfinfo->display, number_buf, number64);

                    label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
                }
            } else {
                number_out = hfinfo_number_value_format64(hfinfo, number_buf, number64);

                label_len = protol_strlcpy(display_label_str, number_out, label_str_size);
            }

            break;

//        case FT_EUI64:
//            tmp_str = eui64_to_str(NULL, fvalue_get_uinteger64(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(tmp_str);
//            break;

//        case FT_IPv4:
//            ipv4 = fvalue_get_ipv4(finfo->value);
//            //XXX: Should we ignore the mask?
//            set_address_ipv4(&addr, ipv4);
//            tmp_str = address_to_display(NULL, &addr);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(tmp_str);
//            free_address(&addr);
//            break;

//        case FT_IPv6:
//            ipv6 = fvalue_get_ipv6(finfo->value);
//            set_address_ipv6(&addr, ipv6);
//            tmp_str = address_to_display(NULL, &addr);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            free_address(&addr);
//            break;

//        case FT_FCWWN:
//            set_address (&addr, AT_FCWWN, FCWWN_ADDR_LEN, fvalue_get_bytes_data(finfo->value));
//            tmp_str = address_to_display(NULL, &addr);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_ETHER:
//            set_address (&addr, AT_ETHER, FT_ETHER_LEN, fvalue_get_bytes_data(finfo->value));
//            tmp_str = address_to_display(NULL, &addr);
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_GUID:
//            tmp_str = guid_to_str(NULL, fvalue_get_guid(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_REL_OID:
//            bytes = fvalue_get_bytes_data(finfo->value);
//            tmp_str = rel_oid_resolved_from_encoded(NULL, bytes, (int)fvalue_length2(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_OID:
//            bytes = fvalue_get_bytes_data(finfo->value);
//            tmp_str = oid_resolved_from_encoded(NULL, bytes, (int)fvalue_length2(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

//        case FT_SYSTEM_ID:
//            bytes = fvalue_get_bytes_data(finfo->value);
//            tmp_str = print_system_id(NULL, bytes, (int)fvalue_length2(finfo->value));
//            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
//            wmem_free(NULL, tmp_str);
//            break;

        case FT_FLOAT:
        case FT_DOUBLE:
            label_len = (int)fill_display_label_float(finfo, display_label_str);
            break;

        case FT_STRING:
        case FT_STRINGZ:
        case FT_UINT_STRING:
        case FT_STRINGZPAD:
        case FT_STRINGZTRUNC:
            str = fvalue_get_string(finfo->value);
            label_len = (int)label_strcpy(display_label_str, label_str_size, 0, str, label_strcat_flags(hfinfo));
            if (label_len >= label_str_size) {
                /* Truncation occurred. Get the real length
                 * copied (not including '\0') */
                label_len = label_str_size ? label_str_size - 1 : 0;
            }
            break;

        default:
            /* First try ftype string representation */
            tmp_str = fvalue_to_string_repr(finfo->value, FTREPR_DISPLAY, hfinfo->display);
            if (!tmp_str) {
                /* Default to show as bytes */
                bytes = fvalue_get_bytes_data(finfo->value);
                tmp_str = bytes_to_str(bytes, fvalue_length2(finfo->value));
            }
            label_len = protol_strlcpy(display_label_str, tmp_str, label_str_size);
            wmem_free(tmp_str);
            break;
    }
    return label_len;
}

/* Set text of proto_item after having already been created. */
void proto_item_set_text(proto_item *pi, const char *format, ...)
{
    field_info *fi = NULL;
    va_list     ap;

    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL)
        return;

    if (fi->rep) {
        ITEM_LABEL_FREE(fi->rep);
        fi->rep = NULL;
    }

    va_start(ap, format);
    proto_tree_set_representation(pi, format, ap);
    va_end(ap);
}

/* Append to text of proto_item after having already been created. */
void proto_item_append_text(proto_item *pi, const char *format, ...)
{
    field_info *fi = NULL;
    size_t      curlen;
    char       *str;
    va_list     ap;

    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL) {
        return;
    }

    if (!proto_item_is_hidden(pi)) {
        /*
         * If we don't already have a representation,
         * generate the default representation.
         */
        if (fi->rep == NULL) {
            ITEM_LABEL_NEW(fi->rep);
            proto_item_fill_label(fi, fi->rep->representation);
        }
        if (fi->rep) {
            curlen = strlen(fi->rep->representation);
            /* curlen doesn't include the \0 byte.
             * XXX: If curlen + 4 > ITEM_LABEL_LENGTH, we can't tell if
             * the representation has already been truncated (of an up
             * to 4 byte UTF-8 character) or is just at the maximum length
             * unless we search for " [truncated]" (which may not be
             * at the start.)
             * It's safer to do nothing.
             */
            if (ITEM_LABEL_LENGTH > (curlen + 4)) {
                va_start(ap, format);
                str = strdup_vprintf(format, ap);
                va_end(ap);
//                WS_UTF_8_CHECK(str, -1);
                curlen = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, curlen, str, 0);
                if (curlen >= ITEM_LABEL_LENGTH) {
                    /* Uh oh, we don't have enough room.  Tell the user
                     * that the field is truncated.
                     */
                    LABEL_MARK_TRUNCATED_START(fi->rep->representation);
                }
            }
        }
    }
}

/* Prepend to text of proto_item after having already been created. */
void proto_item_prepend_text(proto_item *pi, const char *format, ...)
{
    field_info *fi = NULL;
    size_t      pos;
    char        representation[ITEM_LABEL_LENGTH];
    char       *str;
    va_list     ap;

    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL) {
        return;
    }

    if (!proto_item_is_hidden(pi)) {
        /*
         * If we don't already have a representation,
         * generate the default representation.
         */
        if (fi->rep == NULL) {
            ITEM_LABEL_NEW(fi->rep);
            proto_item_fill_label(fi, representation);
        } else
            (void) g_strlcpy(representation, fi->rep->representation, ITEM_LABEL_LENGTH);

        va_start(ap, format);
        str = strdup_vprintf(format, ap);
        va_end(ap);
//        WS_UTF_8_CHECK(str, -1);
        pos = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, 0, str, 0);
        pos = label_strcpy(fi->rep->representation, ITEM_LABEL_LENGTH, pos, representation, 0);
        /* XXX: As above, if the old representation is close to the label
         * length, it might already be marked as truncated. */
        if (pos >= ITEM_LABEL_LENGTH && (strlen(representation) + 4) <= ITEM_LABEL_LENGTH) {
            /* Uh oh, we don't have enough room.  Tell the user
             * that the field is truncated.
             */
            LABEL_MARK_TRUNCATED_START(fi->rep->representation);
        }
    }
}

static void finfo_set_len(field_info *fi, const int length)
{
    int length_remaining;

    DISSECTOR_ASSERT_HINT(length >= 0, fi->hfinfo->abbrev);
    length_remaining = tvb_captured_length_remaining(fi->ds_tvb, fi->start);
    if (length > length_remaining)
        fi->length = length_remaining;
    else
        fi->length = length;

    /* If we have an FT_PROTOCOL we need to set the length of the fvalue tvbuff as well. */
    if (fvalue_type_ftenum(fi->value) == FT_PROTOCOL) {
        fvalue_set_protocol(fi->value, NULL, NULL, fi->length);
    }

    /*
     * You cannot just make the "len" field of a GByteArray
     * larger, if there's no data to back that length;
     * you can only make it smaller.
     */
    if (fvalue_type_ftenum(fi->value) == FT_BYTES && fi->length > 0) {
        GBytes *bytes = fvalue_get_bytes(fi->value);
        size_t size;
        const void *data = g_bytes_get_data(bytes, &size);
        if ((size_t)fi->length <= size) {
            fvalue_set_bytes_data(fi->value, data, fi->length);
        }
        g_bytes_unref(bytes);
    }
}

void proto_item_set_len(proto_item *pi, const int length)
{
    field_info *fi;

    /* If the item is not visible, we can't set the length because
     * we can't distinguish which proto item this is being called
     * on, since faked items share proto items. (#17877)
     */
    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL)
        return;

    finfo_set_len(fi, length);
}

/*
 * Sets the length of the item based on its start and on the specified
 * offset, which is the offset past the end of the item; as the start
 * in the item is relative to the beginning of the data source tvbuff,
 * we need to pass in a tvbuff - the end offset is relative to the beginning
 * of that tvbuff.
 */
void proto_item_set_end(proto_item *pi, tvbuff_t *tvb, int end)
{
    field_info *fi;
    int length;

    /* As with proto_item_set_len() above */
    TRY_TO_FAKE_THIS_REPR_VOID(pi);

    fi = PITEM_FINFO(pi);
    if (fi == NULL)
        return;

    end += tvb_raw_offset(tvb);
    DISSECTOR_ASSERT(end >= fi->start);
    length = end - fi->start;

    finfo_set_len(fi, length);
}

int proto_item_get_len(const proto_item *pi)
{
    field_info *fi;

    if (!pi)
        return -1;
    fi = PITEM_FINFO(pi);
    return fi ? fi->length : -1;
}

void proto_item_set_bits_offset_len(proto_item *ti, int bits_offset, int bits_len) {
    if (!ti) {
        return;
    }
    FI_SET_FLAG(PNODE_FINFO(ti), FI_BITS_OFFSET(bits_offset));
    FI_SET_FLAG(PNODE_FINFO(ti), FI_BITS_SIZE(bits_len));
}

char *proto_item_get_display_repr(proto_item *pi)
{
    field_info *fi;

    if (!pi)
        return "";
    fi = PITEM_FINFO(pi);
    DISSECTOR_ASSERT(fi->hfinfo != NULL);
    return fvalue_to_string_repr(fi->value, FTREPR_DISPLAY, fi->hfinfo->display);
}

proto_tree *proto_tree_create_root(packet_info *pinfo)
{
    proto_node *pnode;

    /* Initialize the proto_node */
    pnode = malloc(sizeof(proto_tree));
    PROTO_NODE_INIT(pnode);
    pnode->parent = NULL;
    PNODE_FINFO(pnode) = NULL;
    pnode->tree_data = malloc(sizeof(tree_data_t));

    /* Make sure we can access pinfo everywhere */
    pnode->tree_data->pinfo = pinfo;

    /* Don't initialize the tree_data_t. Wait until we know we need it */
    pnode->tree_data->interesting_hfids = NULL;

    /* Set the default to false so it's easier to
     * find errors; if we expect to see the protocol tree
     * but for some reason the default 'visible' is not
     * changed, then we'll find out very quickly. */
    pnode->tree_data->visible = false;

    /* Make sure that we fake protocols (if possible) */
    pnode->tree_data->fake_protocols = true;

    /* Keep track of the number of children */
    pnode->tree_data->count = 0;

    return (proto_tree *)pnode;
}

/* "prime" a proto_tree with a single hfid that a dfilter
 * is interested in. */
void proto_tree_prime_with_hfid(proto_tree *tree, const int hfid)
{
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfid, hfinfo);
    /* this field is referenced by a filter so increase the refcount.
       also increase the refcount for the parent, i.e the protocol.
       Don't increase the refcount if we're already printing the
       type, as that is a superset of direct reference.
    */
    if (hfinfo->ref_type != HF_REF_TYPE_PRINT) {
        hfinfo->ref_type = HF_REF_TYPE_DIRECT;
    }
    /* only increase the refcount if there is a parent.
       if this is a protocol and not a field then parent will be -1
       and there is no parent to add any refcounting for.
    */
    if (hfinfo->parent != -1) {
        hf_info *parent_hfinfo;
        PROTO_REGISTRAR_GET_NTH(hfinfo->parent, parent_hfinfo);

        /* Mark parent as indirectly referenced unless it is already directly
         * referenced, i.e. the user has specified the parent in a filter.
         */
        if (parent_hfinfo->ref_type == HF_REF_TYPE_NONE)
            parent_hfinfo->ref_type = HF_REF_TYPE_INDIRECT;
    }
}

/* "prime" a proto_tree with a single hfid that a dfilter
 * is interested in. */
void proto_tree_prime_with_hfid_print(proto_tree *tree, const int hfid)
{
    hf_info *hfinfo;

    PROTO_REGISTRAR_GET_NTH(hfid, hfinfo);
    /* this field is referenced by an (output) filter so increase the refcount.
       also increase the refcount for the parent, i.e the protocol.
    */
    hfinfo->ref_type = HF_REF_TYPE_PRINT;
    /* only increase the refcount if there is a parent.
       if this is a protocol and not a field then parent will be -1
       and there is no parent to add any refcounting for.
    */
    if (hfinfo->parent != -1) {
        hf_info *parent_hfinfo;
        PROTO_REGISTRAR_GET_NTH(hfinfo->parent, parent_hfinfo);

        /* Mark parent as indirectly referenced unless it is already directly
         * referenced, i.e. the user has specified the parent in a filter.
         */
        if (parent_hfinfo->ref_type == HF_REF_TYPE_NONE)
            parent_hfinfo->ref_type = HF_REF_TYPE_INDIRECT;
    }
}

proto_tree *proto_item_add_subtree(proto_item *pi,    const int idx)
{
    field_info *fi;

    if (!pi)
        return NULL;

    DISSECTOR_ASSERT(idx >= 0 && idx < num_tree_types);

    fi = PITEM_FINFO(pi);
    if (!fi)
        return (proto_tree *)pi;

    fi->tree_type = idx;

    return (proto_tree *)pi;
}

proto_tree *proto_item_get_subtree(proto_item *pi)
{
    field_info *fi;

    if (!pi)
        return NULL;
    fi = PITEM_FINFO(pi);
    if ( (!fi) || (fi->tree_type == -1) )
        return NULL;
    return (proto_tree *)pi;
}
